/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.brillien.core.management;

import com.vii.brillien.core.component.SuperFlow;
import com.vii.brillien.core.component.SuperFlowManager;
import com.vii.brillien.core.component.SuperPresenceManager;
import com.vii.brillien.core.component.SuperUnitManager;
import com.vii.brillien.core.component.apikey.ApiKeyValidatorPresence;
import com.vii.brillien.core.component.chatty.Alice;
import com.vii.brillien.core.component.chatty.Bob;
import com.vii.brillien.core.component.chatty.Karl;
import com.vii.brillien.core.component.db.DataSourceManager;
import com.vii.brillien.core.component.io.FileWatcher;
import com.vii.brillien.core.component.io.Logger;
import com.vii.brillien.core.component.script.Processor;
import com.vii.brillien.core.component.server.*;
import com.vii.brillien.core.component.sso.SubjectPresence;
import com.vii.brillien.core.management.component.MetaServices;
import com.vii.brillien.core.management.component.structure.Configuration;
import com.vii.brillien.ignition.BrillienContext;
import com.vii.brillien.ignition.BrillienStatics;
import com.vii.brillien.ignition.transport.BrillienCommunication;
import com.vii.brillien.kernel.BrillienException;
import com.vii.brillien.kernel.axiom.atomic.Flow;
import com.vii.brillien.kernel.axiom.atomic.Presence;
import com.vii.brillien.kernel.axiom.atomic.PresenceManager;
import com.vii.streamline.services.IOServices;
import com.vii.streamline.services.ThreadServices;
import com.vii.streamline.services.error.StreamLineException;
import com.vii.streamline.services.json.JsonServices;
import com.vii.streamline.services.reflection.Invocation;
import com.vii.streamline.services.reflection.ReflectionServices;
import com.vii.streamline.webserver.WebServer;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;

import static com.vii.brillien.ignition.BrillienContext.*;

/**
 * Service class providing basic services of Brillien application server
 */
public class BrillienServices {

    private static String                                   ID_PREFIX;

    private static long                                     flowSequenceID;

    public static String                                    REFERENCE_DELIMETER                 = ":";

    public static final String                              DEFAULT_ANSWER                      = "Done.";
    public static final String                              NULL_ANSWER                         = "!NULL!";

    public static final String                              COMPONENT_LOADED                    = "loaded";
    public static final String                              COMPONENT_LOADED_NANE               = "componentName";
    public static final String                              REPORT_NEW_ASYNC_COMMUNICATION      = "NAC";
    public static final String                              REPORT_CLOSED_ASYNC_COMMUNICATION   = "CAC";

    public static String                                    LIBRARY_DELIMETER                   = ":";

    public static final String                              NAME_DELIMETER                      = "#";

    public static String                                    VALUE_LE                            = "-";

    public static String                                    VALUE_GE                            = "+";

    /**
     * Liaison instance representing the Commander flow of the Brillien instance.
     */
    public static Liaison                                   liaison;


    public static Flow                                      SUPREME_FLOW;

    public static Flow                                      SERVICES_FLOW;

    public static Logger                                    mainLoggerPresence;


    public static ConcurrentHashMap<String, List<String>>   publishedProcessors;




    static{
        flowSequenceID      = Long.MIN_VALUE;

        ID_PREFIX           = NAME_DELIMETER + BrillienContext.SERVER_NAME + NAME_DELIMETER + BrillienStatics.SYSTEM_ID + NAME_DELIMETER;

        publishedProcessors = new ConcurrentHashMap<String, List<String>>();
    }


    public static void addPublisher( String publisherName, String serviceName ){
        if( !publishedProcessors.containsKey( publisherName ) )
            publishedProcessors.put( publisherName, new LinkedList<String>() );

        publishedProcessors.get( publisherName ).add( serviceName );
    }

    public static boolean isPublisherExists( String publisherName ){
        return publishedProcessors.containsKey( publisherName );
    }

    public static boolean isServiceExists( Invocation invocation ){
        if( invocation.getEntity() == null || invocation.getEntity().equals("") ){
            invocation.setEntity( getServicePublisher( invocation.getMethod() ) );
        }
        return isServiceExists( invocation.getEntity(), invocation.getMethod() );
    }

    public static boolean isServiceExists( String publisherName, String serviceName ){
        return publishedProcessors.containsKey( publisherName ) && publishedProcessors.get( publisherName ).contains(serviceName);
    }

    public static String getServicePublisher( String serviceName ){
        for( String publisher : publishedProcessors.keySet() )
            if( publishedProcessors.get( publisher ).contains(serviceName) )
                return publisher;
        return null;
    }

    /**
     * Generate new unique flow ID
     */
    public static String nextFlowID() {
        return ID_PREFIX + (flowSequenceID++) + System.currentTimeMillis() + System.nanoTime();
    }
    
    /**
     * Generate new unique postfix concatenated with the name of the presence
     */
    public static String newNamePostfix() {
        return ID_PREFIX + System.nanoTime();
    }

    /**
     * Decides whether a string fulfils the version requirement string
     * The version requirement strings may contain "-" or "+" values indicating version intervals:
     * 4+ means 4.0 or above
     * 3- means 3.0 or less
     * @param s version requirement string
     * @param v version to be investigated
     */
    public static boolean isConvenientVersion( String s, float v ){
        return  s== null ||
                (
                    s.endsWith( VALUE_GE )
                    ? v >= Float.valueOf( s.substring(0,s.length()-1) )
                    : (s.endsWith( VALUE_LE )
                        ? v <= Float.valueOf( s.substring(0,s.length()-1) )
                        : v == Float.valueOf( s )
                      )
                );
    }

    /**
     * Generate new unique Job ID
     */
    public static String nextJobID() {
        return "Job:" + System.currentTimeMillis() + System.nanoTime();
    }


    /**
     * Initializing application server services
     */
    public static void initServices() {
        try {
            MetaServices.init();
        } catch (Exception e) {
            exceptionLog( "BrillienServices", "initServices", e );
        }
    }


    /**
     * Get the PresenceManager of a presence type of the given typeName
     * @param caller caller Presense referece
     * @param typeName name of the Presence to be instantiated
     * @return Presence instance
     * @throws BrillienException
     */
    public static <T extends Presence> PresenceManager<T,BrillienCommunication> getPresenceManagerByName( Presence caller, String typeName ) throws BrillienException {
        if( caller == null || typeName == null )
            throw new BrillienException("Not null Caller and name is mandatory!");

        systemLog( Level.CONFIG, "Request for presencemanager by name::" + typeName  + " by caller::" + caller );

        return liaison.getPresenceManagerOf( typeName );
    }
    
    /**
     * Get a Presence instance by typeName
     * @param caller caller Presense referece
     * @param typeName name of the Presence to be instantiated
     * @return Presence instance
     * @throws BrillienException
     */
    public static <T extends Presence> T getPresenceByName( Presence caller, String typeName ) throws BrillienException {
        return BrillienServices.<T>getPresenceByName( caller, typeName, null );
    }

    /**
     * Get a Presence instance with the given instanceName  of the given typeName
     * @param caller caller Presense referece
     * @param typeName name of the Presence to be instantiated
     * @param instanceName name of the instance created.
     * @return Presence instance
     * @throws BrillienException
     */
    public static <T extends Presence> T getPresenceByName( Presence caller, String typeName, String instanceName ) throws BrillienException {
        if( caller == null || typeName == null )
            throw new BrillienException("Not null Caller and name is mandatory!");

        systemLog( Level.CONFIG, "Request for presence by name::" + typeName  + " by caller::" + caller );

        PresenceManager<T,BrillienCommunication> PresenceManager = getPresenceManagerByName( caller, typeName );
        if( PresenceManager == null ) throw new BrillienException("No resource found:: " + typeName);

        return PresenceManager.getInstance( caller, instanceName );
    }

    public static void loadAPI_KEYPresence(Class c) throws BrillienException {
        String akpReferenceName = liaison.registerPresence( c );
        ApiKeyValidatorPresence akp = getPresenceByName( SERVICES_FLOW, akpReferenceName );
        systemLog( Level.CONFIG, "API_KEY Presence name: " + akp.getName() );
        liaison.setApyKeyPresenceName( akp.getName() );
        systemLog( Level.CONFIG, "API_KEY parameters: " + BrillienContext.API_KEY_PARAMETERS );
        akp.setConfiguration( BrillienContext.API_KEY_PARAMETERS );
        systemLog( Level.CONFIG, "API_KEY activated." );
    }
    public static void loadSSOPresence(Class c) throws BrillienException {
        String spReferenceName = liaison.registerPresence( c );
        SubjectPresence sp = getPresenceByName( SERVICES_FLOW, spReferenceName );
        systemLog( Level.CONFIG, "SSO Presence name: " + sp.getName() );
        liaison.setSSOPresenceName( sp.getName() );
        systemLog( Level.CONFIG, "SSO parameters: " + BrillienContext.SSO_PARAMETERS );
        sp.setConfiguration( BrillienContext.SSO_PARAMETERS );
        systemLog( Level.CONFIG, "SSO activated." );
    }

    /**
     * Starter method for application server
     * @throws BrillienException in case of the starting process is not executable 
     */
    public static void startServices( ) throws BrillienException {

        systemLog( Level.INFO, "Starting Brillien services." );

        //System.out.println("Initializing..." );
        systemLog( Level.CONFIG, "Initializing thread pool with size: " + THREAD_POOL_SIZE );

        ThreadServices.init( THREAD_POOL_SIZE );
        
        try{
            Thread.currentThread().setContextClassLoader( primordialClassLoader );

            if( COUCHDB_BOARD ){
                CouchDBServices.timeToRelax();
                systemLog( Level.CONFIG, "Started CouchDB services...");
            }

            systemLog( Level.CONFIG, "Initiating Liaison instance" );
            liaison = new Liaison();
            liaison.setFlowID( liaison.getName() + BrillienServices.nextFlowID() );
            liaison.setAspirationTimeMeasure( BrillienContext.HEARTBEAT_CYCLE_MEASURE );
            liaison.activateAll();
            liaison.activateCallable();
            systemLog( Level.CONFIG, "Liaison instance is done." );

            SUPREME_FLOW = new SuperFlow( BrillienContext.SUPREME_FLOW_ID );
            SUPREME_FLOW.setName( BrillienContext.SUPREME_FLOW_ID );
            liaison.setFlow( SUPREME_FLOW );
            SERVICES_FLOW = new SuperFlow( BrillienContext.SERVICES_FLOW_ID );
            SERVICES_FLOW.setName( BrillienContext.SERVICES_FLOW_ID );
            SERVICES_FLOW.setFlow( SUPREME_FLOW );
            FlowServices.registerFlow( SUPREME_FLOW ); FlowServices.registerFlow( SERVICES_FLOW );
            systemLog( Level.CONFIG, "System default and resident Flow presences started::" + BrillienContext.SUPREME_FLOW_ID + " " + BrillienContext.SERVICES_FLOW_ID );

            String dataSourceManager = liaison.registerPresence( DataSourceManager.class );
            String flowManager = liaison.registerPresence( SuperFlowManager.class );
            String unitManager = liaison.registerPresence( SuperUnitManager.class );
            String presenceManager = liaison.registerPresence( SuperPresenceManager.class );
            systemLog( Level.CONFIG, "Built in Presence Managers registered::" + dataSourceManager + " " + flowManager + " " + unitManager + " " + presenceManager );

            String flow = liaison.registerPresence( SuperFlow.class );
            systemLog( Level.CONFIG, "Flow type registered::" + flow );

            systemLog( Level.CONFIG, "Initializing main Logger..." );
            String logger = liaison.registerPresence( Logger.class );
            mainLoggerPresence = getPresenceByName( SERVICES_FLOW, logger );
            systemLog( Level.CONFIG, "Started logging framework with::" + mainLogger );

            if( BrillienContext.TEST_MODE ){
                systemLog( Level.CONFIG, "-----------------------------------------" );
                systemLog( Level.CONFIG, "-----------------------------------------" );
                systemLog( Level.CONFIG, "-----------------------------------------" );

                String alice = liaison.registerPresence( Alice.class );
                final String bob = liaison.registerPresence( Bob.class );
                String karl = liaison.registerPresence( Karl.class );

                systemLog( Level.CONFIG, "-----------------------------------------" );
                systemLog( Level.CONFIG, "-----------------------------------------" );
                systemLog( Level.CONFIG, "-----------------------------------------" );

                for( int i=0; i<1; ++i ){
                    ThreadServices.getStpe().execute( new Runnable() {
                        @Override
                        public void run() {
                            try {
                                Alice aliceInstance = getPresenceByName( liaison, "alice" );

                                aliceInstance.palaver();

                                aliceInstance.whisper();

                                aliceInstance.makeNoise();
                            } catch (BrillienException e) {
                                e.printStackTrace();
                            }
                        }
                    } );
                }
            }  else{
                if( JMX_SERVICE_ON ){
                    JMXServices.startJMXServices();
                    systemLog( Level.CONFIG, "Started JMX services at:" + JMX_SERVICE_PORT );
                }

                String loader = liaison.registerPresence( PresenceLoader.class );
                String fw = liaison.registerPresence( FileWatcher.class );
                String rr = liaison.registerPresence( ReferenceRetriever.class );
                String kk = liaison.registerPresence( Keeper.class );
                String processor = liaison.registerPresence( Processor.class );

                loadServerActivationConfig();

                if( BrillienContext.WEBSERVER_ON ){
                    systemLog( Level.CONFIG, "Trying to start the Web server..." );

                    WebServer.logger = LoggerFactory.getLogger(WebServer.class);

                    WebServer.HOST = WEBSERVER_HOSTNAME;
                    WebServer.PORT = WEBSERVER_PORT;
                    WebServer.DOC_ROOT = WEBSERVER_DOC_ROOT;

                    WebServer.initServer();

                    systemLog( Level.CONFIG, "Web server initiated." );
                }

                try{
                    Class c = primordialClassLoader.loadClass( "com.vii.brillien.services.ServiceLoader" );
                    ReflectionServices.invokeMethod("startServices", c );
                } catch( Throwable t ){
                    exceptionLog(BrillienServices.class.getName(), "startServices", t);
                }

                if( BrillienContext.WEBSERVER_ON ){
                    WebServer.startServer();
                    systemLog( Level.CONFIG, "Web server started." );
                }

                if( BrillienContext.SSO_ACTIVATED ){
                    systemLog( Level.CONFIG, "Trying to load SSO Provider Presence.." );
                    try{
                        loadSSOPresence(primordialClassLoader.loadClass(BrillienContext.SSO_PROVIDER));
                    } catch (Exception e){
                        systemLog( Level.CONFIG, "SSO Provider loading failed! Need to be load from deployed applications!" );
                    }
                }

                if( BrillienContext.API_KEY_ACTIVATED ){
                    systemLog( Level.CONFIG, "Trying to load API_KEY Provider Presence.." );
                    try{
                        loadAPI_KEYPresence(primordialClassLoader.loadClass(BrillienContext.API_KEY_PROVIDER));
                    } catch (Exception e){
                        systemLog( Level.CONFIG, "API_KEY Provider loading failed! Need to be load from deployed applications!" );
                    }
                }

                FileWatcher watcher = getPresenceByName( SERVICES_FLOW, "FileWatcher");
                watcher.setFileToWatch(new File(BrillienStatics.ACTIVATOR_DIR));
                watcher.setAspirationTimeMeasure(ACTIVATOR_CYCLE_MEASURE);
                watcher.activateCallable();

                Presence retriever = getPresenceByName( SERVICES_FLOW, "ReferenceRetriever");
                retriever.setAspirationTimeMeasure(RETRIEVER_CYCLE_MEASURE);
                retriever.activateCallable();

                Presence keeper = getPresenceByName( SERVICES_FLOW, "Keeper");
                keeper.setAspirationTimeMeasure( KEEPER_CYCLE_MEASURE );
                keeper.activateCallable();
            }
            systemLog( Level.INFO, "Started." );
            System.out.println("------------------------");
            System.out.println("Started.");
            System.out.println("------------------------");
        }
        catch(Exception e){
            e.printStackTrace();
            exceptionLog( BrillienServices.class.getName(), "startServices", e );
            stopServices();
        }
    }

    private static void loadServerActivationConfig() throws IOException, ClassNotFoundException, BrillienException, StreamLineException {
        if( new File( BrillienStatics.SERVER_CONFIG_FILE ).exists() ){
            Configuration config = JsonServices.<Configuration>parseJSON(IOServices.getStreamAsString(
                    IOServices.getInputStream( BrillienStatics.SERVER_CONFIG_FILE )
            ), Configuration.class);

            systemLog(Level.CONFIG, "Processing activator of the server.");

            MetaServices.registerMetaInfo( BrillienStatics.SYSTEM_ID, config );

            List<String> Presences = MetaServices.getPresences( BrillienStatics.SYSTEM_ID );
            for( String key : Presences ){
                systemLog(Level.CONFIG, "Loading::", key);
                System.out.println("Loading::" + key);

                Class presence = primordialClassLoader.loadClass( key );
                String PresenceName = BrillienServices.liaison.registerPresence( presence );

                PresenceManager manager = BrillienServices.liaison.getPresenceManagerOf( PresenceName );
                try{
                    manager.setParameters(MetaServices.getPresenceParameters(BrillienStatics.SYSTEM_ID, key));
                } catch( Exception e ){ e.printStackTrace(); }

                MetaServices.registerSourceInfo( BrillienStatics.SYSTEM_ID, manager );

                systemLog(Level.CONFIG, "Successfull loading for:: ", key );
                System.out.println("Successfull loading for:: " + key );

                Presence instance = getPresenceByName( SERVICES_FLOW, PresenceName);
            }

            Map<String, String> clones = MetaServices.getClonePresences( BrillienStatics.SYSTEM_ID );
            for( String name : clones.keySet() ){
                systemLog(Level.CONFIG, "Cloning: " + name + " of " + clones.get(name) );
                BrillienServices.liaison.registerPresence( clones.get(name), name );
            }
        }
    }

    /**
     * Stops all services of the running application server
     */
    public static void stopServices() throws BrillienException {
        System.out.println("Stopping services...");
        systemLog( Level.CONFIG, "Stopping services...");

        ThreadServices.scheduleSingleTask(
                new TimerTask(){
                    @Override
                    public void run() {
                        instantlyStopServices();
                    }
                },
                BrillienContext.SHUTDOWN_TOLERANCE
        );
    }

    private static void instantlyStopServices(){
        try{
            systemLog( Level.CONFIG, "Stopping Web srever...");

            if( BrillienContext.WEBSERVER_ON )
                WebServer.stopServer();
        } catch (Throwable e) {
            exceptionLog( BrillienServices.class.getName(), "instantlyStopServices", e );
        }

        try{
            systemLog( Level.CONFIG, "Stopping JMX...");

            if( JMX_SERVICE_ON )
                JMXServices.stopJMXServices();
        } catch (Throwable e) {
            exceptionLog( BrillienServices.class.getName(), "instantlyStopServices", e );
        }

        try {
            systemLog( Level.CONFIG, "Stopping All Presences...");

            liaison.passivateUnitAll();
        } catch (Throwable e) {
            exceptionLog( BrillienServices.class.getName(), "instantlyStopServices", e );
        }

        try{
            if( COUCHDB_BOARD ){
                systemLog( Level.CONFIG, "Stopping CouchDB services...");

                CouchDBServices.closeCouch();
            }
        } catch (Throwable e) {
            exceptionLog( BrillienServices.class.getName(), "instantlyStopServices", e );
        }

        try {
            systemLog( Level.CONFIG, "Stopping Logging...");

            mainLoggerPresence.passivateAll();

            mainHandler.close();
        } catch (Throwable e) {
            exceptionLog( BrillienServices.class.getName(), "instantlyStopServices", e );
        }

        try{
            systemLog( Level.CONFIG, "Stopping Liaison XMPP entity...");
            
            //BrillienContext.xmppMediator.disconnect();
        } catch (Throwable e) {
            exceptionLog( BrillienServices.class.getName(), "instantlyStopServices", e );
        }

        systemLog( Level.CONFIG, "Stopping Thread services...");        
        ThreadServices.stop();
    }

    public static boolean isSystemEntity(Presence caller, String sender) {
        PresenceManager PresenceManager = null;
        try {
            PresenceManager = getPresenceManagerByName( caller, sender );
        } catch (BrillienException e) {
            exceptionLog( BrillienServices.class.getName(), "isSystemEntity", e );
        }
        return PresenceManager != null;
    }
}
